<?php

/**
 * ControllerModuleSocial Controller
 *
 * @copyright  2016 opencart.cn - All Rights Reserved
 * @link       http://www.guangdawangluo.com
 * @author     Edward Yang <yangjin@opencart.cn>
 * @created    2016-11-15 15:01
 * @modified   2016-11-15 15:01
 */
class ControllerExtensionModuleSocial extends Controller
{
    private $socialProviders = ['qq', 'weibo', 'weixin'];
    private $error = array();
    private $provider = '';
    private $loginKey = '';

    public function setProvider($provider)
    {
        if (!in_array($provider, $this->socialProviders)) {
            throw new Exception('Invalid social provider');
        }
        $this->provider = $provider;
        $this->loginKey = "{$provider}_login";
    }

    public function getProvider()
    {
        if (empty($this->provider)) {
            throw new Exception('Empty social provider');
        }
        return $this->provider;
    }

    public function getLoginKey()
    {
        if (empty($this->provider)) {
            throw new Exception('Empty social provider');
        } elseif (empty($this->loginKey)) {
            throw new Exception('Empty login key');
        }
        return $this->loginKey;
    }

    public function index()
    {
        $this->setProvider($this->request->get['provider']);
        if (!$this->customer->isLogged()) {
            $data['client_id'] = $this->config->get("{$this->getLoginKey()}_client_id");
            $data['return_url'] = $this->config->get("{$this->getLoginKey()}_return_url");

            return $this->load->view("extension/module/{$this->getLoginKey()}", $data);
        }
    }

    public function getModel()
    {
        $modelName = "model_extension_module_{$this->getLoginKey()}";
        if (class_exists($modelName)) {
            return $this->$modelName;
        } else {
            $this->load->model("extension/module/{$this->getLoginKey()}");
            return $this->$modelName;
        }
    }

    public function login()
    {
        $this->setProvider($this->request->get['provider']);
        $this->load->model('extension/module/social');
        $this->load->model('account/customer');
        $this->load->model('account/customer_group');

        if ($this->customer->isLogged()) {
            return $this->jsRedirect('logged');
        }

        if (!isset($this->request->get['code'])) {
            if (isset($this->request->get['error']) && isset($this->request->get['error_description'])) {
                $this->getModel()->log('No code returned. Error: ' . $this->request->get['error'] . ', Error Description: ' . $this->request->get['error_description']);
            }
            return $this->jsRedirect('empty_code');
        }

        $response = $this->getModel()->getTokens($this->request->get['code']);
        if (!$response) {
            $this->getModel()->log('Invalid code.');
            return $this->jsRedirect('invalid_code');
        }

        $access_token = $uid = $unionid = '';
        $user = null;
        if ($this->getProvider() == 'weixin') {
            $access_token = $response->access_token;
            $uid = $response->openid;
            $unionid = $response->unionid;
            $user = $this->getModel()->getUserInfo($access_token, $uid);
        } elseif ($this->getProvider() == 'qq') {
            $access_token = $response;
            $uid = $this->getModel()->getOpenid($response);
            $unionid = '';
            $user = $this->getModel()->getUserInfo($access_token, $uid);
        } elseif ($this->getProvider() == 'weibo') {
            $access_token = $response->access_token;
            $uid = $response->uid;
            $unionid = '';
            $user = $this->getModel()->getUserInfo($access_token, $uid);
        }
        if (!$uid && !$unionid) {
            return $this->jsRedirect('invalid_access_token');
        }
        if (!is_object($user)) {
            $this->getModel()->log('Invalid code.');
            return $this->jsRedirect('invalid_code');
        }

        // User exist in database
        $customerInfo = $this->getCustomerFromAuth($unionid, $uid);
        if ($customerInfo) {
            if ($this->validate($customerInfo['customer_id'])) {
                $authData = $this->getAuthData($uid, $unionid, $access_token);
                $this->completeLogin($customerInfo['customer_id'], $customerInfo['email'], $authData);
                return true;
            } else {
                $this->getModel()->log('Could not login to - ID: ' . $customerInfo['customer_id'] . ', Email: ' . $customerInfo['email']);
                return $this->jsRedirect();
            }
        }

        // User not exist in database, then create it.
        $customerId = $this->createCustomer($user, $access_token, $uid, $unionid);
        $this->getModel()->log('Customer ID date_added: ' . $customerId);
        $customerInfo = $this->getCustomerFromAuth($unionid, $uid);
        if ($customerInfo && $this->validate($customerInfo['customer_id'])) {
            $authData = $this->getAuthData($uid, $unionid, $access_token);
            $this->completeLogin($customerId, $customerInfo['email'], $authData);
        } else {
            $this->getModel()->log('Could not login to - ID: ' . $customerId . ', Email: ' . $user->email);
            return $this->jsRedirect();
        }
    }

    public function logout()
    {
        $this->setProvider($this->request->get['provider']);
        if (isset($this->session->data["{$this->getLoginKey()}"])) {
            unset($this->session->data["{$this->getLoginKey()}"]);
        }
    }

    private function jsRedirect($message = '')
    {
        echo '<!--' . $message . '-->';
        echo '<script type="text/javascript">window.opener.location = "' . $this->url->link('account/account', '', 'SSL') . '"; window.close();</script>';
        return false;
    }

    private function getCustomerFromAuth($unionid, $uid)
    {
        $customerInfo = array();
        if ($uid && in_array($this->provider, ['qq', 'weibo'])) {
            $customerInfo = $this->getModel()->getCustomerByUid($uid, $this->getProvider());
        }
        if (!$customerInfo && $unionid && $this->provider == 'weixin') {
            $customerInfo = $this->getModel()->getCustomerByUnionId($unionid, $this->getProvider());
        }
        return $customerInfo;
    }

    private function createCustomer($user, $access_token, $uid, $unionid)
    {
        if ($this->config->get("{$this->getLoginKey()}_customer_group_id")) {
            $customer_group_id = $this->config->get("{$this->getLoginKey()}_customer_group_id");
        } else {
            $customer_group_id = $this->config->get('config_customer_group_id');
        }
        $data = array(
            'customer_group_id' => (int)$customer_group_id,
            'firstname' => $this->getFirstName($user),
            'email' => '', //md5($uid),
            'telephone' => '',
            'fax' => '',
            'password' => uniqid(rand(), true),
            'company' => ''
        );
        $customerId = $this->model_account_customer->addCustomer($data);
        $this->updateFirstName($customerId, $user);
        $authData = array(
            'customer_id' => $customerId,
            'uid' => $uid,
            'unionid' => $unionid,
            'provider' => $this->getProvider(),
            'access_token' => $access_token,
            'date_added' => date('Y-m-d H:i:s'),
            'date_modified' => date('Y-m-d H:i:s')
        );
        $this->getModel()->createAuthentication($authData);
        return $customerId;
    }

    private function getFirstName($user)
    {
        $firstname = '';
        if (isset($user->nickname)) {
            $firstname = $user->nickname;
        } elseif (isset($user->screen_name)) {
            $firstname = $user->screen_name;
        } elseif (isset($user->name)) {
            $firstname = $user->name;
        }
        return $firstname;
    }

    private function updateFirstName($customerId, $user)
    {
        $firstName = $this->getFirstName($user);
        if (!$firstName) {
            $firstName = $this->getProvider() . '_' . $customerId;
        }
        $updateSql = "UPDATE " . DB_PREFIX . "customer SET `firstname` = '{$firstName}' WHERE `customer_id` = '{$customerId}'";
        return $this->db->query($updateSql);
    }

    private function getAuthData($uid, $unionid, $access_token)
    {
        return array(
            'uid' => $uid,
            'unionid' => $unionid,
            'access_token' => $access_token,
            'provider' => $this->getProvider(),
            'date_modified' => date('Y-m-d H:i:s')
        );
    }

    protected function completeLogin($customer_id, $email, $authData)
    {
        unset($this->session->data['guest']);

        // Add to activity log
        $this->load->model('account/activity');

        $activity_data = array(
            'customer_id' => $this->customer->getId(),
            'name' => $this->customer->getFirstName()
        );
        $this->model_account_activity->addActivity('login', $activity_data);
        $this->getModel()->updateAuthentication($authData);

        if (isset($this->session->data["{$this->getLoginKey()}"]['seamless'])) {
            unset($this->session->data["{$this->getLoginKey()}"]['seamless']);
        }

        $this->getModel()->log('Customer logged in - ID: ' . $customer_id . ', Email: ' . $email);
        echo '<script type="text/javascript">window.opener.location = "' . $this->url->link('account/account', '', 'SSL') . '"; window.close();</script>';
    }

    protected function validate($customer_id)
    {
        // Check how many login attempts have been made.
        $login_info = $this->model_account_customer->getLoginAttempts($customer_id);

        if ($login_info && ($login_info['total'] >= $this->config->get('config_login_attempts')) && strtotime('-1 hour') < strtotime($login_info['date_modified'])) {
            $this->error['warning'] = $this->language->get('error_attempts');
        }

        // Check if customer has been approved.
        $customerInfo = $this->model_account_customer->getCustomer($customer_id);

        if ($customerInfo && !$customerInfo['approved']) {
            $this->error['warning'] = $this->language->get('error_approved');
        }

        if (!$this->error) {
            if (!$this->customer->login($customer_id, '', true)) {
                $this->error['warning'] = $this->language->get('error_login');

                $this->model_account_customer->addLoginAttempt($customer_id);
            } else {
                $this->model_account_customer->deleteLoginAttempts($customer_id);
            }
        }

        return !$this->error;
    }
}
